﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Text.RegularExpressions;
using System.IO;
using System.Reflection.Emit;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Linq.Expressions;

namespace AnaxibiaStudios.Smug.Internal
{
  public abstract class SourceBuilderBase
  {
    public List<Assembly> ReferencedAssemblies { get; set; }
    public List<Type> ReferencedTypes { get; set; }
    protected Dictionary<Type, string> _cachedTypeNames;

    public string Source { get; set; }
    public MemberCollection MemberCollection { get; set; }

    public static string MsCorLib { get { return "mscorlib"; } }
    public static string This { get { return "this"; } }
    public static string RaiseEventPrefix { get { return "________EventToRaise"; } }
    public static string Space { get { return " "; } }
    public static string New { get { return "new"; } }
    public static string Public { get { return "public"; } }
    public static string Protected { get { return "protected"; } }
    public static string Private { get { return "private"; } }
    public static string Class { get { return "class"; } }
    public static string Get { get { return "get"; } }
    public static string Set { get { return "set"; } }
    public static string Semicolon { get { return ";"; } }
    public static string LocalWrapperVariable { get { return "___wrapper"; } }
    public static string LocalBaseCallVariable { get { return "___basecall"; } }
    public static string LocalRetValVariable { get { return "___retVal"; } }
    public static string LocalMethodInfoVariable { get { return "___methodInfo"; } }
    public static string Equal { get { return "="; } }
    public static string Comma { get { return ","; } }
    public static string Dot { get { return "."; } }
    public static string Base { get { return "base"; } }
    public static string Return { get { return "return"; } }
    public static string Namespace { get { return "namespace"; } }
    public static string Colon { get { return ":"; } }
    public static string Value { get { return "value"; } }
    public static string Override { get { return "override"; } }
    public static string Virtual { get { return "virtual"; } }
    public static string VoidName { get { return "void"; } }
    public static string GetPrefix { get { return "get_"; } }
    public static string SetPrefix { get { return "set_"; } }
    public static string AddPrefix { get { return "add_"; } }
    public static string RemovePrefix { get { return "remove_"; } }
    public static string Ref { get { return "ref"; } }
    public static string Out { get { return "out"; } }
    public static string Event { get { return "event"; } }
    public static string Var { get { return "var"; } }
    public static string Typeof { get { return "typeof"; } }
    public static string GetField { get { return "GetField"; } }
    public static string GetValue { get { return "GetValue"; } }
    public static string Invoke { get { return "Invoke"; } }
    public static string FieldType { get { return "FieldType"; } }
    public static string GetProperty { get { return "GetProperty"; } }
    public static string Null { get { return "null"; } }
    public static string Pipe { get { return "|"; } }
    public static string Method { get { return "Method"; } }
    public static string OutPrefix { get { return "______out"; } }
    public static string Lambda { get { return "=>"; } }

    public SourceBuilderBase()
    {
      ReferencedAssemblies = new List<Assembly>();
      ReferencedTypes = new List<Type>();
      _cachedTypeNames = new Dictionary<Type, string>();

      TryAddReference(Assembly.GetCallingAssembly());
      TryAddReference(Assembly.GetEntryAssembly());
      TryAddReference(Assembly.GetExecutingAssembly());
      TryAddReference(Assembly.GetAssembly(typeof(Action)));
      TryAddReference(Assembly.GetAssembly(typeof(System.Linq.Expressions.Expression<>)));
    }

    /// <summary>
    /// Add a list of AssemblyName's to the cache.
    /// </summary>
    /// <param name="names">List to add</param>
    public void TryAddReferences(IEnumerable<Assembly> asms)
    {
      foreach (var asm in asms)
      {
        TryAddReference(asm);
      }
    }

    /// <summary>
    /// Add a single AssemblyName to the static cache.
    /// </summary>
    /// <param name="asm">AssemblyName to add</param>
    public void TryAddReference(Assembly asm)
    {
      if (
        asm == null ||
        asm.GetName().Name == MsCorLib ||
        ReferencedAssemblies.FirstOrDefault(existing => existing.GetName().FullName == asm.GetName().FullName) != null)
      {
        return;
      }

      ReferencedAssemblies.Add(asm);
    }

    /// <summary>
    /// Adds the assembly from which type comes, as well as the assemblies inherited by type
    /// </summary>
    /// <param name="type">type used to lookup needed assemblies</param>
    public void TryAddReference(Type type)
    {
      if (type == null)
      {
        return;
      }

      if (ReferencedTypes.Contains(type))
      {
        return;
      }

      ReferencedTypes.Add(type);

      TryAddReference(type.Assembly);

      foreach (Type inherited in type.GetInterfaces())
      {
        TryAddReference(inherited);
      }

      TryAddReference(type.BaseType);
    }
  }

  public class SourceBuilder : SourceBuilderBase
  {
    public bool IgnoreIndexer { get; set; }
    public bool IgnoreItem { get; set; }

    public SourceBuilder(Type targetType)
    {
      TargetType = targetType;

      if (TargetType != null)
        CreateMemberCollection();
    }

    public string ClassName
    {
      get
      {
        string name = TargetType.Name;

        if (TargetType.IsGenericType)
        {
          int tickIndex = name.IndexOf("`");
          name = name.Substring(0, tickIndex);
        }

        return string.Format("Proxy{0}", name);
      }
    }

    public Type TargetType { get; set; }
    public string TargetNamespace { get { return string.Format("{0}", TargetType.Namespace); } }

    public virtual void GenerateSource()
    {
      VerifyProxiable();
      BuildProxySourceCode();
      Source = IndentFormat(Format(Source), 0);
    }

    public virtual void VerifyProxiable()
    {
      if (
        (TargetType.IsNested && (TargetType.IsNestedPrivate || !TargetType.IsNestedPublic)) ||
        TargetType.IsNotPublic)
      {
        throw new SmugCannotProxyNonPublicTypesException(TargetType);
      }

      if (TargetType.IsSealed)
      {
        throw new SmugCannotProxySealedTypesException(TargetType);
      }

      if (!TargetType.IsInterface && GetConstructors().Count == 0)
      {
        throw new SmugNoConstructorsException(TargetType);
      }

      if (ItemIndexerConflict())
      {
        throw new SmugIndexerConflictException(TargetType);
      }

      if (TargetType.ContainsGenericParameters)
      {
        throw new SmugUndefinedGenericArgumentException(TargetType);
      }

      if (HasAbstractInternals())
      {
        throw new SmugHasAbstractInternalsException(TargetType);
      }
    }

    public virtual bool HasAbstractInternals()
    {
      foreach (var member in TargetType.GetMembers(Constants.Flags))
      {
        var method = MemberInfoEx.SoloWrap(this, member);

        if (method.IsNull)
          continue;

        if (!method.IsAbstract)
          continue;

        if (method.IsPublic)
          continue;

        if (method.IsInternal)
          return true;
      }

      return false;
    }

    public bool ItemIndexerConflict()
    {
      // if the target type has an indexer AND an Item
      // and if both require override, fail
      // if both can have override, choose one (indexer?)

      var indexer = TargetType.GetProperties(Constants.Flags).FirstOrDefault(pinfo => pinfo.GetIndexParameters().Length > 0);
      if (indexer == null)
        return false;

      var item = TargetType.GetProperties(Constants.Flags).FirstOrDefault(pinfo => pinfo.Name == "Item" && pinfo.GetIndexParameters().Length == 0);
      if (item == null)
        return false;

      bool indexerVirtual = Valid(indexer);
      if (!indexerVirtual)
        return false;

      bool itemVirtual = Valid(item);
      if (!itemVirtual)
        return false;

      //so now we know they're both virtual, are they both needed?
      bool indexerOverrideRequired = NeedsDefinition(indexer);
      bool itemOverrideRequired = NeedsDefinition(item);

      if (indexerOverrideRequired && itemOverrideRequired)
        return true;

      IgnoreIndexer = itemOverrideRequired;

      // if indexer is not ignored, item should be (preference to indexer)
      IgnoreItem = !IgnoreIndexer;

      return false;
    }

    private string IndentValue = " ";
    public virtual string IndentFormat(string source, int depth)
    {
      int nextOpen = source.IndexOf("{");
      int nextClose = source.IndexOf("}");
      int nextLine = source.IndexOf("\r\n");

      bool openNext = IsMinIndex(nextOpen, nextClose, nextLine);
      bool closeNext = IsMinIndex(nextClose, nextOpen, nextLine);
      bool lineNext = IsMinIndex(nextLine, nextOpen, nextClose);

      if (openNext)
      {
        source =
          source.Substring(0, nextOpen + 1) +
          IndentFormat(source.Substring(nextOpen + 1), depth + 1);
      }
      else if (closeNext)
      {
        source =
          source.Substring(0, nextClose + 1) +
          IndentFormat(source.Substring(nextClose + 1), depth - 1);
      }
      else if (lineNext)
      {
        // if line is only a close, indent one less
        string line = source.Substring(nextLine).Trim();
        int d = depth;
        if (line.StartsWith("}"))
          d--;

        source =
          source.Substring(0, nextLine + 2) +
          string.Join("", Enumerable.Repeat(IndentValue, d)) +
          IndentFormat(source.Substring(nextLine + 2), depth);
      }

      return source;
    }

    private bool IsMinIndex(int index, params int[] compares)
    {
      if (index == -1)
        return false;

      bool isMin = true;

      foreach (int comp in compares)
      {
        if (comp == -1)
          continue;

        if (comp < index)
        {
          isMin = false;
          break;
        }
      }

      return isMin;
    }

    public virtual string Format(string source)
    {
      source = source.Replace("  ", " ");

      //new lines
      //protect single line
      string single = "{[ a-zA-Z0-9,_]*}[^{};]*;";
      var rx = new Regex(single);
      var match = rx.Match(source);

      if (match.Success)
      {
        source =
          Format(source.Substring(0, match.Index)) +
          match.Value + "\r\n" +
          Format(source.Substring(match.Index + match.Length));

        return source;
      }

      string saveLambda = "__________lambda________";
      source = source.Replace(Lambda, saveLambda);
      source = source.Replace("=", " = ");
      source = source.Replace(saveLambda, "=>");
      source = source.Replace("{", "\r\n{\r\n");
      source = source.Replace("}", "\r\n}\r\n");
      source = source.Replace(";", ";\r\n");
      source = source.Replace("\r\n\r\n", "\r\n");
      source = source.Replace(",", ", ");

      return source;
    }

    public virtual void TryAddProxyReferences()
    {
      TryAddReferences(TargetType.Assembly.GetReferencedAssemblies().Select(refd => Assembly.Load(refd.FullName)));
      TryAddReference(Assembly.GetAssembly(TargetType));

      foreach (var genericType in TargetType.GetGenericArguments())
      {
        TryAddReference(Assembly.GetAssembly(genericType));
      }
    }

    public virtual void BuildProxySourceCode()
    {
      Source = string.Empty;
      TryAddProxyReferences();

      string classSource = "";

      classSource += Namespace + Space + TargetNamespace;

      var namespaceBody = new Scope(Scope.WrapTypeEnum.Brackets);
      var classBody = new Scope(Scope.WrapTypeEnum.Brackets);

      namespaceBody.Add("");
      namespaceBody.Add(Class + Space + ClassName + Space + Colon + Space);
      namespaceBody.Add(BuildTypeName(TargetType));
      namespaceBody.Add(BuildInterfaceInheritance());

      classBody.Add(BuildConstructors());

      classBody.Add(MemberCollection.BuildSource());

      classBody.Add(BuildIProxy());

      namespaceBody.Add(classBody.ToString());
      classSource += namespaceBody.ToString();

      Source = classSource;
    }

    public virtual string BuildInterfaceInheritance()
    {
      string source = Comma;

      string infs = string.Join(Comma + Space, TargetType.GetInterfaces().Select(inf => BuildTypeName(inf)));
      source += (infs.Length == 0 ? string.Empty : (infs + Comma));

      source += BuildTypeName(typeof(Advanced.IProxy));

      return source;
    }

    private void CreateMemberCollection()
    {
      var faces = new List<Type>();
      faces.Add(TargetType);

      MemberCollection = new MemberCollection(this);

      // only interfaces count for interface targets
      // with classes, all the members we care
      // about come from the target type only
      if (TargetType.IsInterface)
        faces.AddRange(TargetType.GetInterfaces());

      foreach (var face in faces)
      {
        foreach (var member in GetAllMembers(face))
          MemberCollection.Add(member);
      }
    }

    public IEnumerable<MemberInfo> GetAllMembers(Type type)
    {
      var reflectors = new Func<BindingFlags, MemberInfo[]>[4];

      reflectors[0] = type.GetMethods;
      reflectors[1] = type.GetEvents;
      reflectors[2] = type.GetProperties;
      reflectors[3] = flags => GetConstructors().Cast<MemberInfo>().ToArray();

      foreach (var mem in reflectors
        .SelectMany(reflector => reflector(Constants.Flags))
        .Where(mem => mem.DeclaringType == type)
        .Where(mem => Valid(mem))
        )
      {
        yield return mem;
      }
    }

    public virtual string BuildEvent(EventInfoEx ex)
    {
      var ev = ex.Info;

      string source = "";

      source += Scope.Comment("Build Event info: {0}", ev.Name);

      if (NeedsDefinition(ev))
      {
        source += BuildEventProperty(ex);
      }

      return source;
    }

    public virtual bool NeedsDefinition(MemberInfo minfo)
    {
      if (minfo.DeclaringType.IsClass && !minfo.DeclaringType.IsAbstract)
      {
        return false;
      }

      return
        minfo is PropertyInfo ? GetMostPublicPropertyMethod((PropertyInfo)minfo).IsAbstract :
        minfo is MethodInfo ? ((MethodInfo)minfo).IsAbstract :
        minfo is EventInfo ? GetAddMethod((EventInfo)minfo).IsAbstract :
        false;
    }

    public virtual MethodInfo GetAddMethod(EventInfo ev)
    {
      return ev.GetAddMethod() ?? ev.GetAddMethod(true);
    }

    public virtual MethodInfo GetRemoveMethod(EventInfo ev)
    {
      return ev.GetRemoveMethod() ?? ev.GetRemoveMethod(true);
    }

    public virtual string BuildEventProperty(EventInfoEx ex)
    {
      var ev = ex.Info;
      var eventType = ev.EventHandlerType;
      MethodInfo eventMethodInfo = GetAddMethod(ev);

      var eventTypeName = BuildTypeName(eventType);

      string source = "";
      source += ex.IsExplicit ? string.Empty : (ex.BuildAccess() + Space);
      source += ex.IsExplicit ? string.Empty : (BuildOverride(eventMethodInfo) + Space);
      source += Event + Space;
      source += eventTypeName + Space;
      source += ex.IsExplicit ? ex.ExplicitPrefix : string.Empty;
      source += ev.Name;

      if (ex.IsExplicit)
      {
        source += Scope.Brackets( "add" + Scope.Brackets() + "remove" + Scope.Brackets() );
      }
      else
      {
        source += Semicolon;
      }

      return source;
    }

    public virtual string BuildGetEventMeta()
    {
      string source = string.Empty;

      var mi = typeof(Advanced.IProxy).GetMethod("GetEventMeta");
      source += MemberInfoEx.SoloWrap(this, mi).BuildSignature();

      var body = new Scope(Scope.WrapTypeEnum.Brackets);

      //var meta = new EventMeta(eventInfo);
      string einfo = mi.GetParameters()[0].Name;
      string meta = "meta";
      body.Add(Var + Space + meta + Equal + New + Space + BuildTypeName(typeof(Advanced.EventMeta)) + Scope.Parenthesis(einfo) + Semicolon);

      //var typ = eventInfo.DeclaringType;
      string type = "typ";
      body.Add(Var + Space + type + Equal + einfo + Dot + "DeclaringType" + Semicolon);

      //var evt = typ.GetField(eventInfo.Name, global::AnaxibiaStudios.Smug.Advanced.Constants.Flags);
      string evt = "evt";
      string flags = BuildTypeName(typeof(Constants)) + Dot + "Flags";
      body.Add(Var + Space + evt + Equal + type + Dot + GetField + Scope.Parenthesis(einfo + Dot + "Name" + Comma + flags) + Semicolon);

      //var Method = evt.FieldType.GetProperty("Method", global::AnaxibiaStudios.Smug.Advanced.Constants.Flags);
      string method = "Method";
      body.Add(Var + Space + method + Equal + evt + Dot + "FieldType" + Dot + "GetProperty" + Scope.Parenthesis(Scope.Quote(method) + Comma + flags) + Semicolon);

      //var Target = evt.FieldType.GetProperty("Target", global::AnaxibiaStudios.Smug.Advanced.Constants.Flags);
      string target = "Target";
      body.Add(Var + Space + target + Equal + evt + Dot + "FieldType" + Dot + "GetProperty" + Scope.Parenthesis(Scope.Quote(target), Comma + flags) + Semicolon);

      //var instance = evt.GetValue(this);
      string instance = "instance";
      body.Add(Var + Space + instance + Equal + evt + Dot + GetValue + Scope.Parenthesis(This) + Semicolon);

      //var raiseRef = Target.GetValue(instance, null);
      string raiseRef = "raiseRef";
      body.Add(Var + Space + raiseRef + Equal + target + Dot + GetValue + Scope.Parenthesis(instance + Comma + Null) + Semicolon);

      //var raiseMethod = (global::System.Reflection.MethodInfo)Method.GetValue(instance, null);
      string raiseMethod = "raiseMethod";
      body.Add(Var + Space + raiseMethod + Equal + Scope.Parenthesis(BuildTypeName(typeof(MethodInfo))) + method + Dot + GetValue + Scope.Parenthesis(instance + Comma + Null) + Semicolon);

      //meta.Raise = () => raiseMethod.Invoke(raiseRef, args);
      string raise = "Raise";
      string args = "args";
      body.Add(meta + Dot + raise + Equal + args + Lambda + raiseMethod + Dot + Invoke + Scope.Parenthesis(raiseRef + Comma + args) + Semicolon);

      string listener = "listener";
      Func<string, string, string> getSingle = (access, bf) =>
        einfo + Dot + "Get" + access + "Method" + Scope.Parenthesis(bf);

      Func<string, string> getAccess = access =>
        Var + Space + access + Equal + getSingle(access, "true") + "??" + getSingle(access, "false") + Semicolon;

      Func<string, string> buildInvoke = access =>
        getAccess(access) +
        meta + Dot + access + Equal + listener + Lambda + access + Dot + Invoke + Scope.Parenthesis(This + Comma + New + Space + "object" + Scope.Square() + Scope.Brackets(listener)) + Semicolon;

      //var remover = eventInfo.GetRemoveMethod(true) ?? eventInfo.GetRemoveMethod(false);
      //meta.Remove = listener => remover.Invoke(this, new object[] { listener });
      body.Add(buildInvoke("Remove"));

      //var adder = eventInfo.GetAddMethod(true) ?? eventInfo.GetAddMethod(false);
      //meta.Add = listener => remover.Invoke(this, new object[] { listener });
      body.Add(buildInvoke("Add"));

      //body.Add(PrepOuts(eventMethodInfo.GetParameters()));
      //body.Add(retValAssign);
      //body.Add(raiseMethod + Dot + Invoke + Scope.Parenthesis(raiseRef + Comma + LocalWrapperVariable) + Semicolon);
      //body.Add(AssignOuts(eventMethodInfo.GetParameters()));

      //return meta;
      body.Add(Return + Space + meta + Semicolon);

      source += body.ToString();

      return source;
    }

    public virtual List<ConstructorInfo> GetConstructors()
    {
      var ctors = new List<ConstructorInfo>();

      if (TargetType.IsInterface)
      {
        ctors.Add(new DefaultConstructorInfo(TargetType));
        return ctors;
      }

      foreach (var ctor in TargetType.GetConstructors(Constants.Flags))
      {
        if (ctor.IsPrivate)
          continue;

        if (MemberInfoEx.DetermineInternal(ctor))
          continue;

        if (ctor.IsStatic)
          continue;

        ctors.Add(ctor);
      }

      return ctors;
    }

    public virtual string BuildConstructors()
    {
      string source = "";

      foreach (var ctor in MemberCollection.Constructors)
      {
        source += BuildConstructor(ctor.Info);
      }

      return source;
    }

    public virtual string BuildConstructor(ConstructorInfo ctor)
    {
      string source = string.Empty;

      var ctorParms = new List<ParameterInfo>();
      ctorParms.Add(new ProxyConfigurationParameterInfo());
      ctorParms.AddRange(ctor.GetParameters());

      source += Public + Space + ClassName + Scope.Parenthesis(BuildParams(false, false, false, null, ctorParms));

      //do call base ctor if base is interface
      if (!TargetType.IsInterface)
        source += Colon + Base + Scope.Parenthesis(BuildParams(false, true, false, null, ctor.GetParameters()));

      source += Scope.Brackets(typeof(InvocationHandler).Name + Equal + ctorParms[0].Name + Dot + typeof(InvocationHandler).Name + Semicolon);

      return source;
    }

    public virtual bool Valid(PropertyInfo pi)
    {
      if (IsIgnored(pi))
      {
        return false;
      }

      return true;
    }

    public virtual bool IsIgnored(PropertyInfo pi)
    {
      var isIndexer = pi.GetIndexParameters().Length > 0;
      var isItem = pi.GetIndexParameters().Length == 0 && pi.Name == "Item";

      if (IgnoreItem && isItem)
      {
        return true;
      }

      if (IgnoreIndexer && isIndexer)
      {
        return true;
      }

      return false;
    }

    public virtual string BuildProperty(PropertyInfoEx ex)
    {
      var pi = ex.Info;
      var explicitRequired = ex.IsExplicit;

      string propSource = string.Empty;

      var parms = pi.GetIndexParameters();
      bool isIndexer = parms.Length > 0;

      Func<bool, string> getPropParams = ignoreTypes => isIndexer ? Scope.Square(BuildParams(false, ignoreTypes, false, null, parms)) : string.Empty;

      string access = explicitRequired ? string.Empty : (PropertyIsPublic(pi) ? Public : Protected);
      string prefix = explicitRequired ? (BuildTypeName(pi.DeclaringType) + Dot) : string.Empty;
      string name = isIndexer ? This : pi.Name;
      string over = explicitRequired ? string.Empty : BuildOverride(pi);

      propSource += access + Space + over + Space + BuildTypeName(pi.PropertyType) + Space + prefix + name + getPropParams(false);

      var propBody = new Scope(Scope.WrapTypeEnum.Brackets);

      string invoke = isIndexer ? getPropParams(true) : (Dot + name);

      propBody.Add(BuildGet(ex, invoke));
      propBody.Add(BuildSet(ex, invoke));

      propSource += propBody.ToString();

      return propSource;
    }

    public virtual bool PropertyIsPublic(PropertyInfo pi)
    {
      var method = GetMostPublicPropertyMethod(pi);

      return method.IsPublic;
    }

    public virtual string BuildGet(PropertyInfoEx pi, string invoke)
    {
      var get = pi.Getter;
      if (get.IsNull || get.IsPrivate)
        return string.Empty;

      string source = "";
      source += get.BuildAccess(pi) + Get;

      var body = new Scope(Scope.WrapTypeEnum.Brackets);
      body.Add(BuildRedirection(get, BuildGetPropertyHandle(get, invoke)));
      source += body.ToString();

      return source;
    }

    public virtual string BuildSet(PropertyInfoEx pi, string invoke)
    {
      var set = pi.Setter;
      if (set.IsNull || set.IsPrivate)
        return string.Empty;

      string source = "";
      source += set.BuildAccess(pi) + Set;

      var body = new Scope(Scope.WrapTypeEnum.Brackets);
      body.Add(BuildRedirection(set, BuildGetPropertyHandle(set, invoke)));
      source += body.ToString();

      return source;
    }

    public virtual MethodInfo GetMostPublicPropertyMethod(PropertyInfo pi)
    {
      var get = GetGetProperty(pi);
      var set = GetSetProperty(pi);

      if (get == null || get.IsPrivate)
      {
        return set;
      }

      if (set == null || set.IsPrivate)
      {
        return get;
      }

      if (set.IsPublic)
      {
        return set;
      }

      return get;
    }

    public virtual MethodInfo GetGetProperty(PropertyInfo pi)
    {
      if (!pi.CanRead)
      {
        return null;
      }

      MethodInfo get = pi.GetGetMethod(false);

      if (get == null)
      {
        get = pi.GetGetMethod(true);
      }

      return get;
    }

    public virtual MethodInfo GetSetProperty(PropertyInfo pi)
    {
      if (!pi.CanWrite)
      {
        return null;
      }

      MethodInfo set = pi.GetSetMethod(false);

      if (set == null)
      {
        set = pi.GetSetMethod(true);
      }

      return set;
    }

    public virtual string BuildTemplate(params Type[] types)
    {
      if (types.Length == 0)
      {
        return "";
      }

      return Scope.Angle(string.Join(", ", types.Select(t => BuildTypeName(t))));
    }

    public static bool IsVoid(Type type)
    {
      return type == typeof(void);
    }

    public virtual string BuildParams(bool ignoreRefs, bool ignoreTypes, bool ignoreNames, string prefix, IEnumerable<ParameterInfo> parameters)
    {
      var types = new List<Type>();
      var names = new List<string>();
      var byref = new List<bool?>();

      foreach (var parm in parameters)
      {
        byref.Add(
          !ignoreRefs && parm.ParameterType.IsByRef ? !parm.IsOut : (bool?)null);

        types.Add(parm.ParameterType);

        names.Add((parm.ParameterType.IsByRef ? (prefix ?? string.Empty) : string.Empty) + parm.Name);
      }

      return BuildParams(byref, ignoreTypes ? null : types, ignoreNames ? null : names);
    }

    public virtual string BuildParams(IEnumerable<bool?> refOutMods, IEnumerable<Type> types, IEnumerable<string> names)
    {
      int len = types != null ? types.Count() : names != null ? names.Count() : 0;

      string paramString = "";
      bool first = true;

      for (int i = 0; i < len; i++)
      {
        if (!first)
        {
          paramString += Comma + Space;
        }
        first = false;

        if (refOutMods != null)
        {
          paramString += refOutMods.ElementAt(i).HasValue ?
              (refOutMods.ElementAt(i).Value ? Ref : Out) + Space : "";
        }

        if (types != null)
        {
          paramString += BuildTypeName(types.ElementAt(i));
        }

        if (types != null && names != null)
        {
          paramString += Space;
        }

        if (names != null)
        {
          paramString += names.ElementAt(i);
        }
      }

      return paramString;
    }

    public virtual string PlainMemberName(MemberInfo member, bool removeExplicit, bool removePrefix, bool removeTemplate)
    {
      string name = "";

      name = member.Name;

      if (removeTemplate)
      {
        // remove template
        int templateIndex = name.IndexOf("<");
        if (templateIndex > 0)
        {
          name = name.Substring(0, templateIndex);
        }
      }

      if (removeExplicit)
      {
        //remove explicit
        int explicitIndex = name.LastIndexOf(".");
        if (explicitIndex > 0)
        {
          name = name.Substring(explicitIndex + 1);
        }
      }

      if (removePrefix)
      {
        if (MemberIsPropertyAccessor(member, name))
        {
          name = name.Substring(4);
        }
        else if (MemberIsEventAccessor(member, name))
        {
          name = name.StartsWith(AddPrefix) ? name.Substring(AddPrefix.Length) : name.Substring(RemovePrefix.Length);
        }
      }

      return name;
    }

    public virtual bool MemberIsPropertyAccessor(MemberInfo member, string name)
    {
      var method = member as MethodInfo;

      // only methodinfo's are property accessor's (not eventinfo)
      if (method == null)
      {
        return false;
      }

      bool getPrefixed = name.StartsWith(GetPrefix);
      bool setPrefixed = name.StartsWith(SetPrefix);

      if (getPrefixed || setPrefixed)
      {
        // just because a method begins with set_ or get_ doesn't mean it's a property!
        // if the declaring type has a property with the same name, without the set_ or get_ prefix
        // then, and only then, can we assume it is a property accessor

        var methodParams = method.GetParameters().Select(pi => pi.ParameterType);
        int methodParamsCount = methodParams.Count();

        // note that a property setter would have at least one parameter
        if (setPrefixed && methodParamsCount == 0)
          return false;

        Type retType = getPrefixed ? method.ReturnType : methodParams.Last();
        Type[] propParams = getPrefixed ? methodParams.ToArray() : methodParams.ToList().GetRange(0, methodParamsCount - 1).ToArray();

        var type = member.DeclaringType;
        string nameWithoutPrefix = name.Substring(GetPrefix.Length);
        string potentialExplicitPrefix = member.Name.Substring(0, member.Name.Length - name.Length);

        var propertyByTheSameName = type.GetProperty(potentialExplicitPrefix + nameWithoutPrefix, Constants.Flags, null, retType, propParams, null);

        // now, if the property was found, then our method is surely a property accessor
        if (propertyByTheSameName != null)
        {
          return true;
        }
      }

      return false;
    }

    public virtual bool MemberIsEventAccessor(MemberInfo member, string nameWithPrefix)
    {
      // only methodinfo's are property accessor's (not eventinfo)
      if (!(member is MethodInfo))
      {
        return false;
      }

      if (nameWithPrefix.StartsWith(AddPrefix) || nameWithPrefix.StartsWith(RemovePrefix))
      {
        // like properties, just because a method begins with the add_ or remove_ prefix does not mean it
        // is an even accessor

        // first look for an event by this name
        var type = member.DeclaringType;

        int prefixLength = nameWithPrefix.StartsWith(AddPrefix) ? AddPrefix.Length : RemovePrefix.Length;
        string nameWithoutPrefix = nameWithPrefix.Substring(prefixLength);
        string nameWithExplicit = GetExplicitPrefix(member, false);

        var eventByTheSameName = type.GetEvent(nameWithExplicit + nameWithoutPrefix, Constants.Flags);

        if (eventByTheSameName != null)
        {
          return true;
        }
      }

      return false;
    }

    public virtual string GetExplicitPrefix(MemberInfo member, bool recreateExplicit)
    {
      if (recreateExplicit)
      {
        string declaringTypeName = BuildTypeName(member.DeclaringType);
        string noGlobal = declaringTypeName.Substring("global::".Length);
        return noGlobal + Dot;
      }
      else
      {
        int dotIndex = member.Name.LastIndexOf(Dot);

        if (dotIndex == -1)
          return string.Empty;

        return member.Name.Substring(0, dotIndex + 1);
      }
    }

    public virtual string BuildMethodName(MethodInfo method)
    {
      return PlainMemberName(method, true, true, true) + BuildTemplate(method.GetGenericArguments());
    }

    public virtual bool Valid(MemberInfo member)
    {
      if (member is EventInfo)
        return true; //all events are fair game

      if (member is PropertyInfo)
        return Valid(member as PropertyInfo);

      if (member is ConstructorInfo)
        return true;

      return Valid(member as MethodInfo, false);
    }

    public virtual bool Valid(MethodInfo method, bool includeProperties = false)
    {
      var badMethods = new List<string>()
            {
                "Finalize",
                "MemberwiseClone",

                "ToString",
                "Equals",
                "GetHashCode",
            };

      string name = PlainMemberName(method, true, false, true);

      if (badMethods.Contains(name))
      {
        return false;
      }

      //don't override events
      if (MemberIsEventAccessor(method, name))
      {
        return false;
      }

      if (!includeProperties)
      {
        if (MemberIsPropertyAccessor(method, name))
        {
          return false;
        }
      }

      return true;
    }

    public virtual string BuildGetPropertyHandle(MethodInfoEx ex, string invoke)
    {
      var method = ex.Info;
      string source = string.Empty;

      bool isSet = IsVoid(method.ReturnType);

      source += BuildGetMethodHandle(
        ex,
        (invoke + (isSet ? (Equal + Value) : string.Empty)),
        BuildGetExplicitProperty(method)
        );

      return source;
    }

    public virtual string BuildGetExplicitMethod(MethodInfo method, string methodCall)
    {
      return
      BuildTypeName(typeof(InvocationHandler)) +
      Dot + "GetExplicitMethod" +
      Scope.Parenthesis
      (
        Scope.Parenthesis() + Lambda +
        Scope.Parenthesis(Scope.Parenthesis(BuildTypeName(method.DeclaringType)) + Null) + Dot +
        BuildMethodName(method) + methodCall + Comma +
        This + Comma +
        LocalWrapperVariable
      );
    }

    public virtual string BuildGetExplicitProperty(MethodInfo method)
    {
      string source = string.Empty;

      bool isSet = IsVoid(method.ReturnType);
      Type propertyType = isSet ? method.GetParameters().Select(pi => pi.ParameterType).Last() : method.ReturnType;

      string cast = isSet ? string.Empty : Scope.Parenthesis(BuildTypeName(typeof(Func<object>)));

      source += cast;

      string invocation = string.Empty;
      invocation += BuildTypeName(typeof(InvocationHandler));
      invocation += Dot;
      invocation += "GetExplicitProperty";
      invocation += Scope.Parenthesis
      (
        Typeof + Scope.Parenthesis(BuildTypeName(method.DeclaringType)) + Comma +
        Scope.Quote(PlainMemberName(method, true, true, true)) + Comma +
        This + Comma +
        New + Space + "object" + Scope.Square() + Scope.Brackets(BuildParams(true, true, false, null, method.GetParameters())) + Comma +
        Typeof + Scope.Parenthesis(BuildTypeName(propertyType)) + Comma +
        (isSet ? "true" : "false")
      );

      invocation = isSet ? invocation : Scope.Parenthesis(invocation);
      source += invocation;
      source = isSet ? source : Scope.Parenthesis(source);

      return source;
    }

    public virtual string BuildGetMethodHandle(MethodInfoEx ex, string methodCall, string explicitAccessor)
    {
      var method = ex.Info;
      string source = string.Empty;

      if (TargetType.IsInterface || TargetType.IsAbstract)
        return string.Empty;

      source += Var + Space + LocalBaseCallVariable + Equal;

      // if the parent is explicit - we must call it with reflection
      string caller = method.IsAbstract ? explicitAccessor : Base;

      string callReturn = IsVoid(method.ReturnType) ? string.Empty : (Var + Space + OutPrefix + LocalRetValVariable + Equal);

      var lambdaBody = new Scope(Scope.WrapTypeEnum.Brackets);
      lambdaBody.Add(callReturn + caller + (method.IsAbstract ? Scope.Parenthesis() : methodCall) + Semicolon);
      lambdaBody.Add(PrepAssignOuts(method.GetParameters()));
      lambdaBody.Add(IsVoid(method.ReturnType) ? string.Empty : (Return + Space + Scope.Parenthesis(BuildTypeName(method.ReturnType)) + OutPrefix + LocalRetValVariable + Semicolon));

      source += Scope.Parenthesis(
        BuildDelegateCastName(method) +
        Scope.Parenthesis
        (
          Scope.Parenthesis() +
          Lambda + lambdaBody.ToString()
        ));

      return source;
    }

    public virtual string BuildMethodCallParams(MethodInfo method, string prefix)
    {
      string source = string.Empty;
      source += Scope.Parenthesis(BuildParams(false, true, false, prefix, method.GetParameters()));

      return source;
    }

    public virtual string BuildDelegateCastName(MethodInfo method)
    {
      string source = string.Empty;

      var retType = method.ReturnType;

      if (IsVoid(retType))
      {
        source += Scope.Parenthesis(BuildTypeName(typeof(Action)));
      }
      else
      {
        var untypedFunc = typeof(Func<>);
        var func = untypedFunc.MakeGenericType(retType);

        source += Scope.Parenthesis(BuildTypeName(func));
      }

      return source;
    }

    public virtual string BuildRedirection(MethodInfoEx ex, string getMethodHandle)
    {
      var method = ex.Info;
      string source = string.Empty;

      bool hasBaseCall = !string.IsNullOrEmpty(getMethodHandle);

      source += PrepOuts(method.GetParameters());
      source += getMethodHandle + (hasBaseCall ? Semicolon : string.Empty);
      source +=
        Var + Space + LocalMethodInfoVariable +
        Equal +
        Scope.Parenthesis(BuildTypeName(typeof(MethodInfo))) +
        Scope.Parenthesis(New + Space + BuildTypeName(typeof(StackTrace)) + Scope.Parenthesis() + Dot + "GetFrame" + Scope.Parenthesis(0.ToString()) + Dot + "GetMethod" + Scope.Parenthesis()) +
        Semicolon;

      string retSet = IsVoid(method.ReturnType) ? string.Empty : (Var + Space + LocalRetValVariable + Equal + Scope.Parenthesis(BuildTypeName(method.ReturnType)));
      string retCast = IsVoid(method.ReturnType) ? string.Empty : (Return + Space + LocalRetValVariable + Semicolon);

      source += retSet;

      string baseCallInvoke =
        hasBaseCall ?
        (Scope.Parenthesis() + Lambda + LocalBaseCallVariable +
        Dot + Method + Dot + Invoke + Scope.Parenthesis(LocalBaseCallVariable + Dot + "Target" + Comma + New + Space + typeof(object).Name.ToLower() + Scope.Square() + Scope.Brackets())) :
        Null;

      var invocType = typeof(InvocationHandler);
      source +=
        BuildTypeName(invocType) + Dot + "GetOrDefault" + Scope.Parenthesis(invocType.Name) + Dot +
        "Handle" + Scope.Parenthesis(
        This + Comma +
        LocalMethodInfoVariable + Comma +
        baseCallInvoke + Comma +
        LocalWrapperVariable) + Semicolon;

      source += AssignOuts(method.GetParameters());
      source += retCast;

      return source;
    }

    public virtual string PrepOuts(ParameterInfo[] parameters)
    {
      string source = "";

      string parms = "";
      string comma = "";

      foreach (var pi in parameters)
      {
        string name = pi.Name;

        if (pi.ParameterType.IsByRef)
        {
          name = OutPrefix + name;

          Type type = pi.ParameterType.GetElementType();
          string defaultValue = string.Format("default({0});", BuildTypeName(type));
          source += Var + Space + OutPrefix + pi.Name + Equal + defaultValue;

          if (pi.IsOut)
          {
            source += pi.Name + Equal + defaultValue;
          }
        }

        parms += comma + name;
        comma = Comma + Space;
      }

      source += Var + Space + LocalWrapperVariable + Equal + New + Space +
        BuildTypeName(typeof(object)) +
        Scope.Square() + Space +
        Scope.Brackets(parms) +
        Semicolon;

      return source;
    }

    public virtual string PrepAssignOuts(ParameterInfo[] parameters)
    {
      string source = string.Empty;

      for (int i = 0; i < parameters.Length; i++)
      {
        var pi = parameters[i];
        if (pi.ParameterType.IsByRef)
        {
          source += LocalWrapperVariable + Scope.Square(i.ToString()) + Equal + OutPrefix + pi.Name + Semicolon;
        }
      }

      return source;
    }

    public virtual string AssignOuts(ParameterInfo[] parameters)
    {
      string source = string.Empty;

      foreach (var pi in EachByRef(parameters, true, true))
      {
        source += pi.Name + Equal +
          Scope.Parenthesis(BuildTypeName(pi.ParameterType)) +
          LocalWrapperVariable + Scope.Square(pi.Position.ToString()) + Semicolon;
      }

      return source;
    }

    public virtual IEnumerable<ParameterInfo> EachByRef(ParameterInfo[] parameters, bool refs, bool outs)
    {
      foreach (var pi in parameters)
      {
        if (!pi.ParameterType.IsByRef)
        {
          continue;
        }

        if ((refs && outs) || (refs && !pi.IsOut) || (outs && pi.IsOut))
        {
          yield return pi;
        }
      }
    }

    public virtual string BuildIProxy()
    {
      string source = string.Empty;

      source += Public + Space + BuildTypeName(typeof(IInvocationHandler)) + Space + typeof(InvocationHandler).Name +
        Scope.Brackets(Get + Semicolon + Set + Semicolon);

      source += BuildGetEventMeta();

      return source;
    }

    public virtual string BuildOverride(PropertyInfo pi)
    {
      return BuildOverride(pi.GetAccessors(true)[0]);
    }

    public virtual string BuildOverride(MethodInfo mi)
    {
      if (!mi.DeclaringType.IsInterface)
      {
        return Override;
      }

      return Virtual;
    }

    public static Tuple<string, List<Assembly>, string> BuildInterfaceWrappedMethod(Type type)
    {
      return Tuple.Create("namespace __ { public class ProxyWrapper { public static void Method() {} } }", new List<Assembly>(), "Method");
    }

    public static string TypeName(Type type)
    {
      return new SourceBuilder(null).BuildTypeName(type);
    }

    public virtual string BuildTypeName(Type type)
    {
      return BuildTypeName(type, type.GetGenericArguments());
    }

    private string BuildTypeName(Type type, Type[] genericParameters)
    {
      if (_cachedTypeNames.ContainsKey(type))
      {
        return _cachedTypeNames[type];
      }

      TryAddReference(type);
      string name = UncheckedBuildTypeName(type, genericParameters);
      _cachedTypeNames[type] = name;
      return name;
    }

    private string UncheckedBuildTypeName(Type type, Type[] genericParameters)
    {
      var elementType = (type.IsArray || type.IsByRef) ? type.GetElementType() : type;
      string name = elementType.Name;

      var gens = genericParameters.ToList();
      var myGens = new Type[] { };
      var parentGens = new Type[] { };

      if (elementType.IsGenericType)
      {
        int backticIndex = name.IndexOf('`');
        int tic = backticIndex >= 0 ? int.Parse(name.Substring(backticIndex + 1)) : 0;
        int count = gens.Count;

        if (backticIndex > 0)
        {
          name = name.Substring(0, backticIndex);
        }

        int mine = tic;
        int hers = count - tic;

        if (mine > 0)
        {
          myGens = gens.GetRange(hers, tic).ToArray();
        }

        if (hers > 0)
        {
          parentGens = gens.GetRange(0, hers).ToArray();
        }
      }

      string namespacePrefix = ""; //empty for template types

      if (!elementType.IsGenericParameter)
      {
        if (elementType.DeclaringType != null)
        {
          //before we move upward, if this type has generic parms - but does not contain generic args, they must belong upward
          namespacePrefix = BuildTypeName(elementType.DeclaringType, parentGens);
        }
        else
        {
          namespacePrefix = "global::" + elementType.Namespace;
        }

        namespacePrefix += SourceBuilder.Dot;
      }

      if (elementType.IsGenericType)
      {
        //special nullable condition
        if (elementType.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
          namespacePrefix = ""; //the namespacePrefix is recreated in this next BuildTypeName
          name = TypeName(myGens[0]) + "?";
        }
        else if (myGens.Length > 0) //how else can i determine if the generics belong to this object?!?
        {
          var template = new Scope(Scope.WrapTypeEnum.Angle);

          template.Add(BuildTypeList(myGens));

          name += template.ToString();
        }
        else
        {
          //do nothing?
        }
      }

      if (type.IsArray)
      {
        name += Scope.Square();
      }

      name = name.Replace("&", "");

      return string.Format("{0}{1}", namespacePrefix, name);
    }

    private string BuildTypeList(Type[] types)
    {
      string typeString = "";
      bool first = true;

      foreach (var type in types)
      {
        if (!first)
        {
          typeString += SourceBuilder.Comma + SourceBuilder.Space;
        }

        first = false;

        typeString += BuildTypeName(type);
      }

      return typeString;
    }
  }
}
